1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.codehaus.groovy.runtime;
20
21 import groovy.lang.MetaMethod;
22 import groovy.lang.MetaProperty;
23
24 import java.lang.reflect.Constructor;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashSet;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.Set;
32
33 import org.codehaus.groovy.reflection.CachedClass;
34 import org.codehaus.groovy.reflection.ClassInfo;
35
36
37
38
39
40
41
42
43 public class MethodRankHelper{
44
45
46 public static final int DL_SUBSTITUTION = 10;
47 public static final int DL_DELETE = 10;
48 public static final int DL_TRANSPOSITION = 5;
49 public static final int DL_CASE = 5;
50
51 public static final int MAX_RECOMENDATIONS = 5;
52 public static final int MAX_METHOD_SCORE = 50;
53 public static final int MAX_CONSTRUCTOR_SCORE = 20;
54 public static final int MAX_FIELD_SCORE = 30;
55 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
56
57 private static final class Pair<U,V> {
58 private U u;
59 private V v;
60 public Pair(U u, V v){
61 this.u = u;
62 this.v = v;
63 }
64 }
65
66
67
68
69
70
71
72
73
74
75 public static String getMethodSuggestionString(String methodName, Class type, Object[] arguments){
76 ClassInfo ci = ClassInfo.getClassInfo(type);
77 List<MetaMethod> methods = new ArrayList<MetaMethod>(ci.getMetaClass().getMethods());
78 methods.addAll(ci.getMetaClass().getMetaMethods());
79 List<MetaMethod> sugg = rankMethods(methodName,arguments,methods);
80 StringBuilder sb = new StringBuilder();
81 if (!sugg.isEmpty()){
82 sb.append("\nPossible solutions: ");
83 for(int i = 0; i < sugg.size(); i++) {
84 if(i != 0) sb.append(", ");
85 sb.append(sugg.get(i).getName()).append("(");
86 sb.append(listParameterNames(sugg.get(i).getParameterTypes()));
87 sb.append(")");
88 }
89 }
90 Class[] argumentClasses = getArgumentClasses(arguments);
91 List<Pair<Class,Class>> conflictClasses = getConflictClasses(sugg,argumentClasses);
92 if (!conflictClasses.isEmpty()){
93 sb.append("\nThe following classes appear as argument class and as parameter class, ");
94 sb.append("but are defined by different class loader:\n");
95 boolean first = true;
96 for(Pair<Class,Class> pair: conflictClasses) {
97 if (!first) {
98 sb.append(", ");
99 } else {
100 first = false;
101 }
102 sb.append(pair.u.getName()).append(" (defined by '");
103 sb.append(pair.u.getClassLoader());
104 sb.append("' and '");
105 sb.append(pair.v.getClassLoader());
106 sb.append("')");
107 }
108 sb.append("\nIf one of the method suggestions matches the method you wanted to call, ");
109 sb.append("\nthen check your class loader setup.");
110 }
111 return sb.toString();
112 }
113
114 private static List<Pair<Class,Class>> getConflictClasses(List<MetaMethod> sugg, Class[] argumentClasses) {
115 List<Pair<Class,Class>> ret = new LinkedList<Pair<Class,Class>>();
116 Set<Class> recordedClasses = new HashSet<Class>();
117 for (MetaMethod method : sugg) {
118 Class[] para = method.getNativeParameterTypes();
119 for (Class aPara : para) {
120 if (recordedClasses.contains(aPara)) continue;
121 for (Class argumentClass : argumentClasses) {
122 if (argumentClass == null) continue;
123 if (argumentClass == aPara) continue;
124 if (argumentClass.getName().equals(aPara.getName())) {
125 ret.add(new Pair<Class, Class>(argumentClass, aPara));
126 }
127 }
128 recordedClasses.add(aPara);
129 }
130 }
131 return ret;
132 }
133
134 private static Class[] getArgumentClasses(Object[] arguments) {
135 Class[] argumentClasses = new Class[arguments.length];
136 for (int i=0; i<argumentClasses.length; i++) {
137 Object arg = arguments[i];
138 if (arg==null) continue;
139 argumentClasses[i] = arg.getClass();
140 }
141 return argumentClasses;
142 }
143
144
145
146
147
148
149
150
151
152 public static String getConstructorSuggestionString(Class type, Object[] arguments){
153 Constructor[] sugg = rankConstructors(arguments, type.getConstructors());
154 if(sugg.length >0){
155 StringBuilder sb = new StringBuilder();
156 sb.append("\nPossible solutions: ");
157 for(int i = 0; i < sugg.length; i++){
158 if(i != 0) sb.append(", ");
159 sb.append(type.getName()).append("(");
160 sb.append(listParameterNames(sugg[i].getParameterTypes()));
161 sb.append(")");
162 }
163 return sb.toString();
164 } else{
165 return "";
166 }
167 }
168
169
170
171
172
173
174
175
176
177 public static String getPropertySuggestionString(String fieldName, Class type){
178 ClassInfo ci = ClassInfo.getClassInfo(type);
179 List<MetaProperty> fi = ci.getMetaClass().getProperties();
180 List<RankableField> rf = new ArrayList<RankableField>(fi.size());
181 StringBuilder sb = new StringBuilder();
182 sb.append("\nPossible solutions: ");
183
184 for(MetaProperty mp : fi) rf.add(new RankableField(fieldName, mp));
185 Collections.sort(rf);
186
187 int i = 0;
188 for (RankableField f : rf) {
189 if (i > MAX_RECOMENDATIONS) break;
190 if (f.score > MAX_FIELD_SCORE) break;
191 if(i > 0) sb.append(", ");
192 sb.append(f.f.getName());
193 i++;
194 }
195 return i > 0? sb.toString(): "";
196 }
197
198
199
200
201
202
203
204 private static String listParameterNames(Class[] cachedClasses){
205 StringBuilder sb = new StringBuilder();
206 for(int i =0; i < cachedClasses.length;i++){
207 if(i != 0) sb.append(", ");
208 sb.append(cachedClasses[i].getName());
209 }
210 return sb.toString();
211 }
212
213
214 private static String listParameterNames(CachedClass[] cachedClasses){
215 StringBuilder sb = new StringBuilder();
216 for(int i =0; i < cachedClasses.length;i++){
217 if(i != 0) sb.append(", ");
218 sb.append(cachedClasses[i].getName());
219 }
220 return sb.toString();
221 }
222
223
224
225
226
227
228
229
230
231
232 private static List<MetaMethod> rankMethods(String name, Object[] original, List<MetaMethod> methods) {
233 List<RankableMethod> rm = new ArrayList<RankableMethod>(methods.size());
234 if (original==null) original = EMPTY_OBJECT_ARRAY;
235 Class[] ta = new Class[original.length];
236
237 Class nullC = NullObject.class;
238 for(int i = 0; i < original.length; i++){
239
240 ta[i] = original[i] == null?nullC: original[i].getClass();
241 }
242
243 for (MetaMethod m:methods) {
244 rm.add(new RankableMethod(name, ta, m));
245 }
246 Collections.sort(rm);
247
248 List<MetaMethod> l = new ArrayList<MetaMethod>(rm.size());
249 for (RankableMethod m : rm) {
250 if (l.size() > MAX_RECOMENDATIONS) break;
251 if (m.score > MAX_METHOD_SCORE) break;
252 l.add(m.m);
253 }
254 return l;
255 }
256
257
258
259
260
261
262 private static final class RankableMethod implements Comparable {
263 final MetaMethod m;
264 final Integer score;
265
266 public RankableMethod(String name, Class[] argumentTypes, MetaMethod m2) {
267 this.m = m2;
268 int nameDist = delDistance(name, m2.getName());
269
270
271 Class[] mArgs = new Class[m2.getParameterTypes().length];
272 for(int i =0; i < mArgs.length; i++){
273
274 mArgs[i] = boxVar(m2.getParameterTypes()[i].getTheClass());
275 }
276 int argDist = damerauLevenshteinDistance(argumentTypes,mArgs);
277 this.score = nameDist + argDist;
278 }
279
280 public int compareTo(Object o) {
281 RankableMethod mo = (RankableMethod) o;
282 return score.compareTo(mo.score);
283 }
284 }
285
286
287
288
289
290
291
292
293
294 private static Constructor[] rankConstructors(Object[] original, Constructor[] candidates) {
295 RankableConstructor[] rc = new RankableConstructor[candidates.length];
296 Class[] ta = new Class[original.length];
297
298 Class nullC = NullObject.class;
299 for (int i = 0; i < original.length; i++) {
300
301 ta[i] = original[i] == null ? nullC : original[i].getClass();
302 }
303
304 for (int i = 0; i < candidates.length; i++) {
305 rc[i] = new RankableConstructor(ta, candidates[i]);
306 }
307 Arrays.sort(rc);
308 List<Constructor> l = new ArrayList<Constructor>();
309 int index = 0;
310 while (l.size() < MAX_RECOMENDATIONS && index < rc.length && rc[index].score < MAX_CONSTRUCTOR_SCORE) {
311 l.add(rc[index].c);
312 index++;
313 }
314 return l.toArray(new Constructor[l.size()]);
315 }
316
317
318
319
320
321
322 private static final class RankableConstructor implements Comparable {
323 final Constructor c;
324 final Integer score;
325
326 public RankableConstructor(Class[] argumentTypes, Constructor c) {
327 this.c = c;
328
329 Class[] cArgs = new Class[c.getParameterTypes().length];
330 for(int i =0; i < cArgs.length; i++){
331
332 cArgs[i] = boxVar(c.getParameterTypes()[i]);
333 }
334
335 this.score = damerauLevenshteinDistance(argumentTypes,cArgs);
336 }
337
338 public int compareTo(Object o) {
339 RankableConstructor co = (RankableConstructor) o;
340 return score.compareTo(co.score);
341 }
342 }
343
344
345
346
347
348
349 private static final class RankableField implements Comparable {
350 final MetaProperty f;
351 final Integer score;
352
353 public RankableField(String name, MetaProperty mp) {
354 this.f = mp;
355 this.score = delDistance(name,mp.getName());
356 }
357
358 public int compareTo(Object o) {
359 RankableField co = (RankableField) o;
360 return score.compareTo(co.score);
361 }
362 }
363
364
365
366
367
368
369
370
371 protected static Class boxVar(Class c){
372 if(Boolean.TYPE.equals(c)){
373 return Boolean.class;
374 }else if(Character.TYPE.equals(c)){
375 return Character.class;
376 }else if(Byte.TYPE.equals(c)){
377 return Byte.class;
378 }else if(Double.TYPE.equals(c)){
379 return Double.class;
380 }else if(Float.TYPE.equals(c)){
381 return Float.class;
382 }else if(Integer.TYPE.equals(c)){
383 return Integer.class;
384 }else if(Long.TYPE.equals(c)){
385 return Long.class;
386 }else if(Short.TYPE.equals(c)){
387 return Short.class;
388 }else{
389 return c;
390 }
391 }
392
393
394
395
396 private static class NullObject{
397 }
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416 public static int delDistance(CharSequence s, CharSequence t) {
417 if (s == null || t == null) {
418 throw new IllegalArgumentException("Strings must not be null");
419 }
420
421 int n = s.length();
422 int m = t.length();
423
424 if (n == 0) {
425 return m;
426 } else if (m == 0) {
427 return n;
428 }
429
430
431 int[][] vals = new int[3][n + 1];
432
433
434 int _d[];
435
436
437 int i;
438 int j;
439
440 char t_j;
441 char s_i;
442 int cost;
443
444 for (i = 0; i <= n; i++) {
445 vals[1][i] = i * DL_DELETE;
446 }
447
448 for (j = 1; j <= m; j++) {
449 t_j = t.charAt(j - 1);
450 vals[0][0] = j * DL_DELETE;
451
452 for (i = 1; i <= n; i++) {
453 s_i = s.charAt(i - 1);
454 if (Character.isLowerCase(s_i) ^ Character.isLowerCase(t_j)) {
455
456 cost = caselessCompare(s_i, t_j) ? DL_CASE : DL_SUBSTITUTION;
457 } else {
458
459 cost = s_i == t_j ? 0 : DL_SUBSTITUTION;
460 }
461
462
463 vals[0][i] = Math.min(Math.min(vals[0][i - 1] + DL_DELETE, vals[1][i] + DL_DELETE), vals[1][i - 1] + cost);
464
465
466 if (i > 1 && j > 1) {
467 cost = Character.isLowerCase(s_i) ^ Character.isLowerCase(t.charAt(j - 2)) ? DL_CASE : 0;
468 cost = Character.isLowerCase(s.charAt(i - 2)) ^ Character.isLowerCase(t_j) ? cost + DL_CASE : cost;
469
470 if (caselessCompare(s_i, t.charAt(j - 2)) && caselessCompare(s.charAt(i - 2), t_j)) {
471 vals[0][i] = Math.min(vals[0][i], vals[2][i - 2] + DL_TRANSPOSITION + cost);
472 }
473 }
474 }
475
476
477 _d = vals[2];
478 vals[2] = vals[1];
479 vals[1] = vals[0];
480 vals[0] = _d;
481 }
482
483
484
485 return vals[1][n];
486 }
487
488
489
490
491
492
493
494 private static boolean caselessCompare(char a, char b){
495 return Character.toLowerCase(a) == Character.toLowerCase(b);
496 }
497
498
499
500
501
502
503
504
505
506
507
508 public static int damerauLevenshteinDistance(Object[] s, Object[] t) {
509 if (s == null || t == null) {
510 throw new IllegalArgumentException("Arrays must not be null");
511 }
512
513 int n = s.length;
514 int m = t.length;
515
516 if (n == 0) {
517 return m;
518 } else if (m == 0) {
519 return n;
520 }
521
522 int[][] vals = new int[3][n + 1];
523
524
525 int _d[];
526
527
528 int i;
529 int j;
530
531 Object t_j;
532
533 int cost;
534
535 for (i = 0; i <= n; i++) {
536 vals[1][i] = i * DL_DELETE ;
537 }
538
539 for (j = 1; j <= m; j++) {
540 t_j = t[j - 1];
541 vals[0][0] = j * DL_DELETE ;
542
543 for (i = 1; i <= n; i++) {
544 cost = s[i - 1].equals(t_j)? 0 : DL_SUBSTITUTION;
545
546 vals[0][i] = Math.min(Math.min(vals[0][i - 1] + DL_DELETE, vals[1][i] + DL_DELETE), vals[1][i - 1] + cost);
547
548
549 if(i > 1 && j > 1 && s[i -1].equals(t[j -2]) && s[i- 2].equals(t_j)){
550 vals[0][i] = Math.min(vals[0][i], vals[2][i-2] + DL_TRANSPOSITION);
551 }
552 }
553
554
555 _d = vals[2];
556 vals[2] = vals[1];
557 vals[1] = vals[0];
558 vals[0] = _d;
559 }
560
561 return vals[1][n];
562 }
563 }